package checker

import (
	"math/big"
)

type Number struct {
	ch rune
	char bool
	float bool
	nan bool			// Not a Number
	infinity bool
	value *big.Float
}

var zeroFloatNumber = NewNumberWithString("0", true)
var zeroIntegerNumber = NewNumberWithString("0", false)
var positiveInfinityNumber = NewInfinityNumber()
var negativeInfinityNumber = NewNegativeInfinityNumber()
var nanNumber = NewNanNumber()


func NewNumber(isFloat bool) *Number{
	var result = new(Number)
	result.value = new(big.Float)
	result.float = isFloat
	return result
}

func NewInfinityNumber() *Number {
	var result = new(Number)
	result.value = new(big.Float)
	result.value.SetInt64(10)
	result.infinity = true
	result.float = true
	return result
}

func NewNegativeInfinityNumber() *Number {
	var result = new(Number)
	result.value = new(big.Float)
	result.value.SetFloat64(-10)
	result.infinity = true
	result.float = true
	return result
}

func NewNanNumber() *Number {
	var result = new(Number)
	result.value = new(big.Float)
	result.nan = true
	result.float = true
	return result
}

func NewNumberWithString(value string, isFloat bool) *Number {
	var result = new(Number)
	result.value = new(big.Float)
	result.value.SetString(value)
	result.float = isFloat
	return result
}

func NewNumberWithRune(ch rune) *Number {
	var result = new(Number)
	result.value = new(big.Float)
	result.value.SetInt64(int64(ch))
	result.char = true
	return result
}

func NewNumberWithNumber(number *Number) *Number {
	var result = new(Number)
	result.value = new(big.Float)
	result.value.Set(number.value)
	result.float = number.float
	result.nan = number.nan
	result.infinity = number.infinity
	return result
}

func (n *Number) eraseValue() float64{
	val, _ := n.value.Float64()
	if n.infinity {
		return val
	}

	if val > 0 {
		return 1
	} else if val < 0 {
		return -1
	}
	return 0
}

func (n *Number) Add(input *Number) *Number {

	var floatingPointArithmetic = n.float || input.float

	if floatingPointArithmetic {
		if n.nan || input.nan {
			return nanNumber
		}

		if n.infinity || input.infinity {
			var val1 = n.eraseValue()
			var val2 = input.eraseValue()
			var res = val1 + val2

			if n.infinity && input.infinity {
				if res == 0 {
					return nanNumber
				} else if res > 0 {
					return positiveInfinityNumber
				} else {
					return negativeInfinityNumber
				}

			} else {
				if res < 0 {
					return negativeInfinityNumber
				}
				return positiveInfinityNumber
			}
		}
	}

	var result = NewNumber(floatingPointArithmetic)
	result.value.Set(n.value)
	result.value.Add(n.value, input.value)
	return result
}

func (n *Number) Sub(input *Number) *Number {
	var floatingPointArithmetic = n.float || input.float

	if floatingPointArithmetic {
		if n.nan || input.nan {
			return nanNumber
		}

		if n.infinity || input.infinity {
			var val1 = n.eraseValue()
			var val2 = input.eraseValue()
			var res = val1 + val2

			if n.infinity && input.infinity {
				if res == 0 {
					return nanNumber
				} else if res > 0 {
					return positiveInfinityNumber
				} else {
					return negativeInfinityNumber
				}
			} else {
				if res < 0 {
					return negativeInfinityNumber
				}
				return positiveInfinityNumber
			}
		}
	}


	var result = NewNumber(floatingPointArithmetic)
	result.value.Set(n.value)
	result.value.Sub(n.value, input.value)
	return result
}

func (n *Number) Mul(input *Number) *Number {
	var floatingPointArithmetic = n.float || input.float

	if floatingPointArithmetic {
		if n.nan || input.nan {
			return nanNumber
		}

		if n.infinity || input.infinity {
			var val1 = n.eraseValue()
			var val2 = input.eraseValue()
			var res = val1 * val2

			if n.infinity && input.infinity {
				if res > 0 {
					return positiveInfinityNumber
				}
				return negativeInfinityNumber
			} else {
				if res == 0 {
					return nanNumber
				} else if res > 0 {
					return positiveInfinityNumber
				} else {
					return negativeInfinityNumber
				}
			}
		}
	}

	var result = NewNumber(floatingPointArithmetic)
	result.value.Set(n.value)
	result.value.Mul(n.value, input.value)
	return result
}

func (n *Number) Div(input *Number) *Number {
	var floatingPointArithmetic = n.float || input.float
	if floatingPointArithmetic {
		if n.nan || input.nan {
			return nanNumber
		}

		if n.infinity || input.infinity {
			var val1 = n.eraseValue()
			var val2 = input.eraseValue()

			if n.infinity && input.infinity {
				var res = val1/val2
				if res > 0 {
					return positiveInfinityNumber
				}
				return negativeInfinityNumber
			} else {
				if val1 == 0 {
					return zeroFloatNumber
				}
				if val2 == 0 {
					return nanNumber
				}

				var res = val1/val2
				 if res > 0 {
					return positiveInfinityNumber
				} else {
					return negativeInfinityNumber
				}
			}
		}

		if input.Cmp(zeroFloatNumber) == 0 {
			if n.IsZero() {
				return nanNumber
			} else if n.IsNegative() {
				return negativeInfinityNumber
			} else {
				return positiveInfinityNumber
			}
		}
	}

	if input.Cmp(zeroIntegerNumber) == 0 {
		return zeroIntegerNumber
	}

	var result = NewNumber(floatingPointArithmetic)
	result.value.Set(n.value)
	result.value.Quo(n.value, input.value)
	return result
}

func (n *Number) Mod(number *Number) *Number {
	// Not allow float or double
	operand1, _ := n.value.Int64()
	operand2, _ := number.value.Int64()
	var r = operand1%operand2

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) And(number *Number) *Number {
	operand1, _ := n.value.Int64()
	operand2, _ := number.value.Int64()
	var r = operand1&operand2

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) Or(number *Number) *Number {
	opt1, _ := n.value.Int64()
	opt2, _ := number.value.Int64()
	var r = opt1|opt2

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) Xor(number *Number) *Number {
	operand1, _ := n.value.Int64()
	operand2, _ := number.value.Int64()
	var r = operand1^operand2

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) Not() *Number {
	operand, _ := n.value.Int64()
	var r = ^operand

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) Lsh(number *Number) *Number {
	operand1, _ := n.value.Int64()
	operand2, _ := number.value.Int64()
	var r = operand1 << operand2

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) Rsh(number *Number) *Number {
	operand1, _ := n.value.Int64()
	operand2, _ := number.value.Int64()
	var r = operand1 >> operand2

	var result = NewNumber(false)
	result.value.SetInt64(r)
	return result
}

func (n *Number) URsh(number *Number) *Number {
	operand1, _ := n.value.Uint64()
	operand2, _ := number.value.Uint64()
	var r = operand1 >> operand2

	var result = NewNumber(false)
	result.value.SetUint64(r)
	return result
}

func (n *Number) Cmp(number *Number) int {
	return n.value.Cmp(number.value)
}

func (n *Number) IsInfinity() bool {
	return n.infinity
}

func (n *Number) IsNegative() bool {
	val, _ := n.value.Float64()
	return val < 0
}

func (n *Number) IsNan() bool {
	return n.nan
}

func (n *Number) IsZero() bool {
	return n.Cmp(zeroFloatNumber) == 0
}

func (n *Number) String() string {
	if n.char {
		return string(n.ch)
	}
	return n.value.String()
}

func (n *Number) Float() *big.Float {
	return n.value
}